/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.hive.hcatalog.cli.SemanticAnalysis;

import java.io.Serializable;
import java.util.List;

import org.apache.hadoop.hive.metastore.api.Database;
import org.apache.hadoop.hive.ql.exec.Task;
import org.apache.hadoop.hive.ql.metadata.AuthorizationException;
import org.apache.hadoop.hive.ql.metadata.Hive;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.metadata.InvalidTableException;
import org.apache.hadoop.hive.ql.metadata.Partition;
import org.apache.hadoop.hive.ql.metadata.Table;
import org.apache.hadoop.hive.ql.parse.AbstractSemanticAnalyzerHook;
import org.apache.hadoop.hive.ql.parse.HiveSemanticAnalyzerHookContext;
import org.apache.hadoop.hive.ql.parse.SemanticException;
import org.apache.hadoop.hive.ql.plan.DDLWork;
import org.apache.hadoop.hive.ql.security.authorization.HiveAuthorizationProvider;
import org.apache.hadoop.hive.ql.security.authorization.Privilege;
import org.apache.hadoop.hive.ql.session.SessionState;

/**
 * Base class for HCatSemanticAnalyzer hooks.
 */
public class HCatSemanticAnalyzerBase extends AbstractSemanticAnalyzerHook {

  private HiveAuthorizationProvider authProvider;

  public HiveAuthorizationProvider getAuthProvider() {
    if (authProvider == null) {
      authProvider = SessionState.get().getAuthorizer();
    }

    return authProvider;
  }

  @Override
  public void postAnalyze(HiveSemanticAnalyzerHookContext context,
              List<Task<? extends Serializable>> rootTasks) throws SemanticException {
    super.postAnalyze(context, rootTasks);

    //Authorize the operation.
    authorizeDDL(context, rootTasks);
  }

  /**
   * Checks for the given rootTasks, and calls authorizeDDLWork() for each DDLWork to
   * be authorized. The hooks should override this, or authorizeDDLWork to perform the
   * actual authorization.
   */
  /*
  * Impl note: Hive provides authorization with it's own model, and calls the defined
  * HiveAuthorizationProvider from Driver.doAuthorization(). However, HCat has to
  * do additional calls to the auth provider to implement expected behavior for
  * StorageDelegationAuthorizationProvider. This means, that the defined auth provider
  * is called by both Hive and HCat. The following are missing from Hive's implementation,
  * and when they are fixed in Hive, we can remove the HCat-specific auth checks.
  * 1. CREATE DATABASE/TABLE, ADD PARTITION statements does not call
  * HiveAuthorizationProvider.authorize() with the candidate objects, which means that
  * we cannot do checks against defined LOCATION.
  * 2. HiveOperation does not define sufficient Privileges for most of the operations,
  * especially database operations.
  * 3. For some of the operations, Hive SemanticAnalyzer does not add the changed
  * object as a WriteEntity or ReadEntity.
  *
  * @see https://issues.apache.org/jira/browse/HCATALOG-244
  * @see https://issues.apache.org/jira/browse/HCATALOG-245
  */
  protected void authorizeDDL(HiveSemanticAnalyzerHookContext context,
                List<Task<? extends Serializable>> rootTasks) throws SemanticException {

    if (!HCatAuthUtil.isAuthorizationEnabled(context.getConf())) {
      return;
    }

    Hive hive;
    try {
      hive = context.getHive();

      for (Task<? extends Serializable> task : rootTasks) {
        if (task.getWork() instanceof DDLWork) {
          DDLWork work = (DDLWork) task.getWork();
          if (work != null) {
            authorizeDDLWork(context, hive, work);
          }
        }
      }
    } catch (SemanticException ex) {
      throw ex;
    } catch (AuthorizationException ex) {
      throw ex;
    } catch (Exception ex) {
      throw new SemanticException(ex);
    }
  }

  /**
   * Authorized the given DDLWork. Does nothing by default. Override this
   * and delegate to the relevant method in HiveAuthorizationProvider obtained by
   * getAuthProvider().
   */
  protected void authorizeDDLWork(HiveSemanticAnalyzerHookContext context,
                  Hive hive, DDLWork work) throws HiveException {
  }

  protected void authorize(Privilege[] inputPrivs, Privilege[] outputPrivs)
    throws AuthorizationException, SemanticException {
    try {
      getAuthProvider().authorize(inputPrivs, outputPrivs);
    } catch (HiveException ex) {
      throw new SemanticException(ex);
    }
  }

  protected void authorize(Database db, Privilege priv)
    throws AuthorizationException, SemanticException {
    try {
      getAuthProvider().authorize(db, null, new Privilege[]{priv});
    } catch (HiveException ex) {
      throw new SemanticException(ex);
    }
  }

  protected void authorizeTable(Hive hive, String tableName, Privilege priv)
    throws AuthorizationException, HiveException {
    Table table;
    try {
      table = hive.getTable(tableName);
    } catch (InvalidTableException ite) {
      // Table itself doesn't exist in metastore, nothing to validate.
      return;
    }

    authorize(table, priv);
  }

  protected void authorize(Table table, Privilege priv)
    throws AuthorizationException, SemanticException {
    try {
      getAuthProvider().authorize(table, new Privilege[]{priv}, null);
    } catch (HiveException ex) {
      throw new SemanticException(ex);
    }
  }

  protected void authorize(Partition part, Privilege priv)
    throws AuthorizationException, SemanticException {
    try {
      getAuthProvider().authorize(part, new Privilege[]{priv}, null);
    } catch (HiveException ex) {
      throw new SemanticException(ex);
    }
  }
}
